package nio.demo;

import java.net.InetSocketAddress;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;

/**
 * @Desc:   Selector 的应用案例
 * @author: cww
 * @DateTime: 2020-02-03 17:45
 */

public class SelectorDemo {
    public static void main(String[] args) throws Exception {
        Selector selector = Selector.open();
        // 没有实际意义的通道只做示例
        // the port to listen
        int port = 9999;
        // here we create a ServerSocketChannel
        ServerSocketChannel channel = ServerSocketChannel.open();
        // set channel to non-blocking mode, be careful, FileChannel can not be set to non-blocking mode!
        channel.configureBlocking(false);
        // bind address on port
        channel.socket().bind(new InetSocketAddress(port));
        int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;
        SelectionKey key = channel.register(selector, interestSet);
        // get interest set
        int interestSetCount = key.interestOps();
        while (true) {
            // int select() 阻塞到至少有一个通道在你注册事件上就绪了
            int readyChannels = selector.select();
            if (readyChannels == 0) continue;
            Set<SelectionKey> keys = selector.selectedKeys();
            Iterator keyIterator = keys.iterator();
            while (keyIterator.hasNext()) {
                // SelectionKey.channel()方法返回的通道需要转型成你要处理的类型，
                // 如ServerSocketChannel或SocketChannel等
                SelectionKey key1 = (SelectionKey) keyIterator.next();
                SocketChannel socketChannel1 = (SocketChannel) key.channel();
                if (key.isAcceptable()) {
                    // a connection was accepted by a ServerSocketChannel.
                } else if (key.isConnectable()) {
                    // a connection was established with a remote server.
                } else if (key.isReadable()) {
                    // a channel is ready for reading
                } else if (key.isWritable()) {
                    // a channel is ready for writing
                }
                // 注意每次迭代末尾的keyIterator.remove()调用。Selector不会自己从
                // 已选择键集中移除SelectionKey实例。必须在处理完通道时自己移除。
                // 下次该通道变成就绪时，Selector会再次将其放入已选择键集中
                keyIterator.remove();
            }
        }
    }

    /**
     * Selector 方法使用案例
     *
     */
    public void selectorMethod() throws Exception{
        Selector selector = Selector.open();
        SocketChannel socketChannel = SocketChannel.open();
        // 与 selector 一起使用时， Channel 必须处于非阻塞模式下，
        // 这意味着不能将 FileChannel 与 Selector 一起使用，而套接字通道都可以
        socketChannel.configureBlocking(false);
        // register()方法的第二个参数。这是一个“interest集合”，
        // 意思是在通过Selector监听Channel时对什么事件感兴趣。可以监听四种不同类型的事件
        // 1.Connect
        // 2.Accept
        // 3.Read
        // 4.Write
        // 通道触发了一个事件意思是该事件已经就绪。所以，某个channel成功连接到另一个服务器称为“连接就绪”。
        // 一个server socket channel准备好接收新进入的连接称为“接收就绪”。一个有数据可读的通道可以说是“读就绪”。
        // 等待写数据的通道可以说是“写就绪”
        // 同时监听多个事件
        int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;
        SelectionKey key = socketChannel.register(selector, interestSet);
        // 可以用类似 isInterested() 中的方式判断相关事件或操作是否已经就绪，也可以用 isAble() 方法所示直接判断
        int readySet = key.readyOps();
        // 从 SelectionKey 访问 Channel 和 Selector
        Channel channel = key.channel();
        Selector selector1 = key.selector();
        // 可以将一个对象或者更多信息附着到SelectionKey上
        key.attach(new Object());
        Object arrachObject = key.attachment();
        // 也可以在注册Channel的时候添加对象
        SelectionKey key1 = ((SelectableChannel) channel).register(selector, SelectionKey.OP_READ, new Object());

        // 阻塞到至少有一个通道在你注册的事件上就绪了。
        int count = selector.select();
        // 和select()一样，除了最长会阻塞timeout毫秒(参数)。
        int countWithTime = selector.select(5000);
        // 不会阻塞，不管什么通道就绪都立刻返回,如果上一次选择操作后，没有通道变成可选择的（就绪的），则返回0
        int countNow = selector.selectNow();
        // 访问“已选择键集（selected key set）”中的就绪通道，可以这个集合进行遍历 见 traversalSelectionKey()
        Set<SelectionKey> selectedKeys = selector.selectedKeys();

        // 某个线程调用select()方法后阻塞了，即使没有通道已经就绪，也有办法让其从select()方法返回。
        // 只要让其它线程在第一个线程调用select()方法的那个对象上调用Selector.wakeup()方法即可。
        // 阻塞在select()方法上的线程会立马返回
        // 如果有其它线程调用了wakeup()方法，但当前没有线程阻塞在select()方法上，下个调用select()
        // 方法的线程会立即“醒来（wake up）”
        selector.wakeup();
        // 用完Selector后调用其close()方法会关闭该Selector，且使注册到该Selector上的所有SelectionKey实例无效。
        // 通道本身并不会关闭
        selector.close();

    }

    /**
     * 判断 interest 集合是否存在集合中
     */
    public void isInterested(SelectionKey key) {
        int interestSet = key.interestOps();

        boolean isInterestedInAccept = (interestSet & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT;
        boolean isInterestedInConnect = (interestSet & SelectionKey.OP_CONNECT) == SelectionKey.OP_CONNECT;
        boolean isInterestedInRead    = (interestSet & SelectionKey.OP_READ) == SelectionKey.OP_READ;
        boolean isInterestedInWrite   = (interestSet & SelectionKey.OP_WRITE) == SelectionKey.OP_WRITE;
    }

    /**
     *  检测channel中什么事件或操作已经就绪
     * @param key
     */
    public void isAble(SelectionKey key) {
        boolean isAcceptable = key.isAcceptable();
        boolean isConnectable = key.isConnectable();
        boolean isReadable = key.isReadable();
        boolean isWritable = key.isWritable();
    }

    /**
     * 访问已就绪通道
     * @param keys
     */
    public void traversalSelectionKey(Set<SelectionKey> keys) {
        Iterator keyIterator = keys.iterator();
        while(keyIterator.hasNext()) {
            // SelectionKey.channel()方法返回的通道需要转型成你要处理的类型，
            // 如ServerSocketChannel或SocketChannel等
            SelectionKey key = (SelectionKey) keyIterator.next();
            SocketChannel socketChannel = (SocketChannel) key.channel();
            if(key.isAcceptable()) {
                // a connection was accepted by a ServerSocketChannel.
            } else if (key.isConnectable()) {
                // a connection was established with a remote server.
            } else if (key.isReadable()) {
                // a channel is ready for reading
            } else if (key.isWritable()) {
                // a channel is ready for writing
            }
            // 注意每次迭代末尾的keyIterator.remove()调用。Selector不会自己从
            // 已选择键集中移除SelectionKey实例。必须在处理完通道时自己移除。
            // 下次该通道变成就绪时，Selector会再次将其放入已选择键集中
            keyIterator.remove();
        }

    }
}

class Test {
    public static void main(String[] args) {
        int i1 = 1 << 4;
        int i2 = i1 | 1 << 2;
        System.out.println(i2);
    }
}